#include "waveform.h"

#include <stdint.h>
#include <math.h>

#include <iostream>
#include <fstream>
#include <string>
#include <utility>
#include <map>
#include <vector>

namespace ch {
using namespace std;

template<typename T>
void readFile(const std::string path,std::vector<T>& out) {
    std::ifstream tmp(path, std::ios::ate | std::ios::binary);
    size_t size =  tmp.tellg();
    std::cout << "read file " << path << ", data size " << size << std::endl;
    tmp.close();

    std::ifstream inData(path, std::ios::in | std::ios::binary);
    out.resize((size_t)(size/sizeof(T)));
    inData.read(reinterpret_cast<char *>(out.data()), size);
    inData.close();
}

template<typename T>
void readFileToChannel(const std::string path,std::vector<T>* data,size_t channelCount) {
    std::vector<T> tmp;
    readFile(path, tmp);
    size_t singleSize = tmp.size() / channelCount;
    auto it = tmp.begin();
    for (size_t i=0;i<channelCount;i++){
       data[i].assign(it+i*singleSize,it+(i+1)*singleSize);
    }
}

template<typename T>
void writeFile(const std::string path,const std::vector<T>& out) {
    std::ofstream tmp(path, std::ios::ate | std::ios::binary);
    tmp.write(reinterpret_cast<const char *>(out.data()), out.size() * sizeof(T));
    tmp.close();
}

template<typename T>
void writeFileToChannel(const std::string path,const std::vector<T>* data,size_t channelCount) {
    std::vector<T> tmp;
    for (size_t i=0;i<channelCount;i++){
        tmp.insert(tmp.end(), data[i].begin(), data[i].end());
    }
    writeFile(path, tmp);
}

template<typename T>
int swapChannelData(T* x, size_t i, size_t j) {
    if (i >= ChannelCount || j>=ChannelCount) {
        return -1;
    }
    auto tmp = x[i];
    x[i] = x[j];
    x[j] = tmp;
    return 0;
}

Waveform::Waveform() :
    mWaveform(),
    mHeartbeats(),
    mHeartbeatChannel() {
    mHeartbeatChannel[0]=II;
    mHeartbeatChannel[1]=V1;
    mHeartbeatChannel[2]=V5;
}

Waveform::~Waveform() {
}

void Waveform::Load(const std::string dataPath,const std::string heartbeatPath) {
    readFileToChannel(dataPath, mWaveform, ChannelCount);

    vector<uint32_t> hbRaw[ChannelCount+1];
    readFileToChannel(heartbeatPath,hbRaw,ChannelCount+1);
    for (size_t channel=0;channel<ChannelCount+1;channel++) {
        for(size_t tag = 0; tag<TagCount; tag++) {
            size_t sz = 0;
            if(channel == I && tag == Ps) {
                sz = hbRaw[channel][0];
                mHeartbeats.resize(sz);
            } else {
                sz = mHeartbeats.size();
            }

			if (mHeartbeats.size() == 0)
				break;

			int pos = 0;
            for (size_t hbIndex=0;hbIndex<sz;hbIndex++) {
				pos = tag*(sz + 1) + hbIndex + 1;
				if (pos >= sz)
					continue;

				size_t v = hbRaw[channel][pos];
                if (hbIndex == 0 && 0) {
                    cout<<channel << ":" <<tag*sz + hbIndex + 1<<":" << v <<endl;
                }
                if (channel == None) {
                    mHeartbeats[hbIndex].combineTags[tag]=v;
                } else {
                    mHeartbeats[hbIndex].tags[channel][tag] = v;
                }
            }
        }
    }
}

void Waveform::Save(const string dataPath,const string heartbeatPath) {
    writeFileToChannel(dataPath, mWaveform, ChannelCount);
    cout << "finish raw" << endl;
    vector<uint32_t> hbRaw[ChannelCount+1];
    size_t sz = mHeartbeats.size();
    for (size_t channel=0;channel<ChannelCount+1;channel++) {
        hbRaw[channel].resize(TagCount*(sz+1));
        for(size_t tag = 0; tag<TagCount; tag++) {
            hbRaw[channel][tag*(sz+1)] = sz;
            for (size_t hbIndex=0;hbIndex<sz;hbIndex++) {
                //cout << "c:" << channel << ",t:"<<tag << ",hb:"<<hbIndex << endl;
                if (channel == None) {
                    hbRaw[channel][tag*(sz+1) + hbIndex + 1] = mHeartbeats[hbIndex].combineTags[tag];
                } else {
                    hbRaw[channel][tag*(sz+1) + hbIndex + 1] = mHeartbeats[hbIndex].tags[channel][tag];
                }
            }
        }
    }
    writeFileToChannel(heartbeatPath, hbRaw, ChannelCount+1);
}

vector<int16_t> const Waveform::GetWaveform(size_t i) {
    return mWaveform[i];
}

vector<Heartbeat> const Waveform::GetHeartbeats() {
    return mHeartbeats;
}

int Waveform::SwapChannel(Channel i, Channel j) {
    if (i == None || j>= None) {
        return -1;
    }
    swapChannelData(mWaveform, i, j);

    // swap channel of Heartbeats
    size_t hbCount = mHeartbeats.size();
    size_t sz=TagCount * sizeof(size_t);
    size_t tmp[TagCount];
    for(size_t hbIndex = 0; hbIndex< hbCount; hbIndex++) {
        memcpy(tmp, mHeartbeats[hbIndex].tags[i], sz);
        memcpy(mHeartbeats[hbIndex].tags[i], mHeartbeats[hbIndex].tags[j], sz);
        memcpy(mHeartbeats[hbIndex].tags[j], tmp, sz);
    }
    SetHeartbeatChannels(mHeartbeatChannel[0], mHeartbeatChannel[1], mHeartbeatChannel[2]);
    return 0;
}

int Waveform::ReverseChannel(Channel channel) {
    if (channel == None) {
        return -1;
    }
    size_t c = mWaveform[channel].size();
    for(size_t i = 0;i<c;i++) {
        mWaveform[channel][i]=-mWaveform[channel][i];
    }
    return 0;
}

void Waveform::CalculateCombineTags() {
    size_t hbCount= mHeartbeats.size();
    for(size_t i=0;i<hbCount;i++) {
        for(size_t tag = 0; tag<TagCount; tag++) {
            bool showlog = i==0 && tag ==Ps && false;
            size_t total = 0;
            size_t c = 0;
            for (Channel channel:mHeartbeatChannel) {
                if(channel == None) {
                    continue;
                }
                size_t tmp = mHeartbeats[i].tags[channel][tag];
                if(showlog) {
                    cout<<"channel:"<<channel<< " v:"<<tmp<<endl;
                }
                if (tmp > 0) {
                    total += tmp;
                    c += 1;
                }
            }
            if(c==0) {
                mHeartbeats[i].combineTags[tag] =  0;
            } else {
                mHeartbeats[i].combineTags[tag] = round(total * 1.f / c);
                if(showlog) {
                    cout <<"combine tag:" << tag << " v:" << mHeartbeats[i].combineTags[tag] << endl;
                }
            }
        }
    }
}

int Waveform::SetHeartbeatChannels(Channel c1, Channel c2, Channel c3) {
    mHeartbeatChannel[0]=c1;
    mHeartbeatChannel[1]=c2;
    mHeartbeatChannel[2]=c3;
    CalculateCombineTags();
    return 0;
}


int Waveform::SetHeartbeat(const Heartbeat& hb, size_t index){
    if(index>mHeartbeats.size()) {
        return -1;
    }
    mHeartbeats[index]=hb;
    CalculateCombineTags();
    return 0;
}

int Waveform::GetIndication(Indication& ind) {
    CalculateDuration(ind);
    CalculateVolt(ind);
    CalculateAxis(ind);
    return 0;
}

void Waveform::CalculateDuration(Indication& ind) { 
    size_t hbCount = mHeartbeats.size();
	if (hbCount == 0)
		return;

    float RRInterval = (mHeartbeats[hbCount-1].combineTags[R] - mHeartbeats[0].combineTags[R]) *1.f / SamplingRate / (hbCount - 1);
    ind.HBR = 60 / RRInterval;
 
    size_t validCycleCount=0;
    size_t totalTime[4]={0,0,0,0};//P,PR,QRS,QT

    for (size_t i = 0; i<hbCount; i++) {
        size_t ps = mHeartbeats[i].combineTags[Ps];
        size_t pe = mHeartbeats[i].combineTags[Pe];
        size_t q  = mHeartbeats[i].combineTags[Q];
        size_t re = mHeartbeats[i].combineTags[Re];
        size_t te = mHeartbeats[i].combineTags[Te];
        if (ps == 0 || pe == 0 || q ==0 || re == 0 || te == 0 ) {
            continue;
        }
        validCycleCount++;
        totalTime[0] += pe - ps;
        totalTime[1] += q - ps;
        totalTime[2] += re -q;
        totalTime[3] += te - q;
    }
    if (validCycleCount > 0) {
        ind.PDuration   = totalTime[0] * 1.f / validCycleCount / SamplingRate * 1000;
        ind.PRInterval  = totalTime[1] * 1.f / validCycleCount / SamplingRate * 1000;
        ind.QRSDuration = totalTime[2] * 1.f / validCycleCount / SamplingRate * 1000;
        ind.QTInterval  = totalTime[3] * 1.f / validCycleCount / SamplingRate * 1000;
        ind.QTcInterval = ind.QTInterval / sqrt(RRInterval); 
    } else {
        ind.PDuration   = 0;
        ind.PRInterval  = 0;
        ind.QRSDuration = 0;
        ind.QTInterval  = 0;
        ind.QTcInterval = 0;
    }
}

double Waveform::CalculateSingleVolt(Channel channel, HeartbeatTag tag) {
    size_t sz = mHeartbeats.size();
    size_t validCount = 0;
    int32_t totalVolt = 0;
    for (size_t i=0;i<sz;i++){
        size_t pos =  mHeartbeats[i].tags[channel][tag];
        if (pos == 0) {
            continue;
        }
        validCount++;
        totalVolt += mWaveform[channel][pos];
    }
    if(validCount==0){
        return 0.f;
    }
    return totalVolt * Raw2mV / validCount;
}

void Waveform::CalculateVolt(Indication& ind) {
    ind.RV5 = CalculateSingleVolt(V5, R);
    ind.RV6 = CalculateSingleVolt(V6, R);
    ind.SV1 = CalculateSingleVolt(V1, S);
    ind.SV2 = CalculateSingleVolt(V2, S);
}

/**
 * The Quadrant Method
 *
 * Only use Lead 1 and aVF
 */
double Waveform::CalculateSingleAxis(HeartbeatTag tag) {
    double negLead1 = 0.f;
    double negAvf = 0.f;
    if (R == tag) {
        negLead1 = abs(CalculateSingleVolt(I, S));
        negAvf = abs(CalculateSingleVolt(aVF, S));
    }
    double lead1 = CalculateSingleVolt(I, tag)-negLead1;
    double avf = CalculateSingleVolt(aVF, tag)-negAvf;
    double axis = atan(avf/lead1) * 180 / PI;
    if (axis<0) {
        if(avf>0) {
            axis = axis + 180;
        }
    } else {
        if(avf<0) {
            axis = axis - 180;
        }
    }
    return axis;
}
void Waveform::CalculateAxis(Indication& ind) {
    ind.PAxis = CalculateSingleAxis(P);
    ind.QRSAxis = CalculateSingleAxis(R);
    ind.TAxis = CalculateSingleAxis(T);
}
}

